// DOM elements
const canvas = document.getElementById('linkage-canvas');
const ctx = canvas.getContext('2d');
const link0Input = document.getElementById('link0');
const link1Input = document.getElementById('link1');
const link2Input = document.getElementById('link2');
const link3Input = document.getElementById('link3');
const speedInput = document.getElementById('speed');
const startBtn = document.getElementById('start-btn');
const stopBtn = document.getElementById('stop-btn');
const resetBtn = document.getElementById('reset-btn');
const statusMessage = document.getElementById('status-message');
const currentAngleDisplay = document.getElementById('current-angle');
const mechanismTypeDisplay = document.getElementById('mechanism-type');
const zoomInBtn = document.getElementById('zoom-in-btn');
const zoomOutBtn = document.getElementById('zoom-out-btn');
const autoScaleBtn = document.getElementById('auto-scale-btn');
const zoomLevelDisplay = document.getElementById('zoom-level');

// Simulation parameters
let link0Length = parseFloat(link0Input.value); // Ground link
let link1Length = parseFloat(link1Input.value); // Input link
let link2Length = parseFloat(link2Input.value); // Coupler link
let link3Length = parseFloat(link3Input.value); // Follower link
let speed = parseFloat(speedInput.value);
let inputAngle = 0; // Starting angle in radians
let isRunning = false;
let animationId = null;

// Canvas parameters
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
let scale = 2; // Scale factor for visualization
let zoomLevel = 1.0; // Current zoom level

// Colors for the links
const colors = {
    ground: '#333333',
    link1: '#e74c3c',
    link2: '#3498db',
    link3: '#2ecc71',
    joint: '#7f8c8d',
    path: '#bdc3c7'
};

// Store the path of point B for visualization
let pathPoints = [];
const maxPathPoints = 1000; // 增加最大路径点数量以显示更完整的轨迹

// 存储上一个周期的起始角度，用于识别完整周期
let lastCycleStartAngle = 0;
let completedCycles = 0;
const cycleTolerance = 0.1; // 用于确定何时完成一个完整周期的容差

// Calculate auto scale factor based on link lengths
function calculateAutoScale() {
    // Get the maximum possible distance the linkage could reach
    const maxDistance = link0Length + link1Length + link2Length + link3Length;
    
    // Calculate scale to fit within 80% of the canvas
    const canvasMinDimension = Math.min(canvas.width, canvas.height);
    const safeDimension = canvasMinDimension * 0.8;
    
    return safeDimension / maxDistance;
}

// Initialize the simulation
function init() {
    // Clear the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 仅在重置时清空路径点
    if (inputAngle === 0) {
        pathPoints = [];
        lastCycleStartAngle = 0;
        completedCycles = 0;
    }
    
    // Draw initial position
    drawLinkage(0);
    
    // Check if the linkage is valid
    checkLinkageValidity();
    
    // Determine mechanism type
    determineMechanismType();
}

// Apply auto scaling
function applyAutoScale() {
    scale = calculateAutoScale();
    zoomLevel = 1.0;
    updateZoomLevelDisplay();
    
    // Redraw the linkage
    if (!isRunning) {
        drawLinkage(inputAngle);
    }
}

// Update zoom level display
function updateZoomLevelDisplay() {
    zoomLevelDisplay.textContent = `${Math.round(zoomLevel * 100)}%`;
}

// Check if the four-bar linkage is valid based on Grashof's condition
function checkLinkageValidity() {
    // Sort link lengths
    const links = [link0Length, link1Length, link2Length, link3Length].sort((a, b) => a - b);
    const s = links[0]; // Shortest link
    const l = links[3]; // Longest link
    const p = links[1]; // Middle length 1
    const q = links[2]; // Middle length 2
    
    if (s + l > p + q) {
        // Grashof condition not satisfied - linkage can't complete a full rotation
        statusMessage.textContent = '警告：当前连杆配置可能存在运动限制或无法实现';
        statusMessage.className = 'error';
        return false;
    } else {
        statusMessage.textContent = '连杆配置有效';
        statusMessage.className = 'success';
        return true;
    }
}

// Determine mechanism type based on Grashof's condition
function determineMechanismType() {
    const links = [
        { length: link0Length, name: 'Ground Link' },
        { length: link1Length, name: 'Input Link' },
        { length: link2Length, name: 'Coupler Link' },
        { length: link3Length, name: 'Follower Link' }
    ];
    
    // Sort links to identify shortest and longest
    links.sort((a, b) => a.length - b.length);
    const s = links[0]; // Shortest link
    const l = links[3]; // Longest link
    const p = links[1]; // Middle length 1
    const q = links[2]; // Middle length 2
    
    if (s.length + l.length < p.length + q.length) {
        // Grashof condition is satisfied
        
        if (s.name === 'Ground Link') {
            mechanismTypeDisplay.textContent = '机构类型：曲柄摇杆机构（输入杆可完整旋转）';
        } else if (s.name === 'Coupler Link') {
            mechanismTypeDisplay.textContent = '机构类型：双摇杆机构（无完整旋转）';
        } else if (s.name === 'Input Link' || s.name === 'Follower Link') {
            mechanismTypeDisplay.textContent = '机构类型：摇杆曲柄机构（从动杆可完整旋转）';
        }
    } else if (s.length + l.length === p.length + q.length) {
        mechanismTypeDisplay.textContent = '机构类型：换向点机构（特殊情形）';
    } else {
        mechanismTypeDisplay.textContent = '机构类型：非格拉肖夫机构（运动受限）';
    }
}

// Calculate the position of point B (end of the coupler link)
function calculatePosition(angle) {
    // Calculate positions based on the input angle
    // Point A (end of input link)
    const ax = -link0Length / 2 + link1Length * Math.cos(angle);
    const ay = link1Length * Math.sin(angle);
    
    // Calculate the position of point B (end of follower link)
    // This uses the intersection of two circles method
    // One circle centered at A with radius link2Length
    // Another circle centered at right fixed point with radius link3Length
    
    // Right fixed point
    const rightFixedX = link0Length / 2;
    const rightFixedY = 0;
    
    // Distance between A and the right fixed point
    const dA = Math.sqrt(Math.pow(ax - rightFixedX, 2) + Math.pow(ay - rightFixedY, 2));
    
    // Check if the linkage can reach this position
    if (dA > link2Length + link3Length || dA < Math.abs(link2Length - link3Length)) {
        // The linkage cannot reach this position
        return null;
    }
    
    // Calculate using the law of cosines to find the angle between link2 and the line from A to right fixed point
    // We can determine the distance from A to right fixed point (dA)
    // Then use the law of cosines to find the angle between this line and link2
    // Law of cosines: c^2 = a^2 + b^2 - 2ab*cos(C)
    // Rearranging: cos(C) = (a^2 + b^2 - c^2) / (2ab)
    // where a = link2Length, b = dA, c = link3Length
    
    const cosAngle = (Math.pow(link2Length, 2) + Math.pow(dA, 2) - Math.pow(link3Length, 2)) / (2 * link2Length * dA);
    
    // Clamp the cosine value to avoid domain errors due to floating point precision
    const clampedCosAngle = Math.min(1, Math.max(-1, cosAngle));
    
    // Angle of the line from A to right fixed point
    const angleAtoRight = Math.atan2(rightFixedY - ay, rightFixedX - ax);
    
    // Angle of link2 relative to the horizontal 
    // We use the - sign to choose one of two possible configurations
    // This is typically the "elbow down" configuration
    const angleLink2 = angleAtoRight - Math.acos(clampedCosAngle);
    
    // Calculate point B position using precise trigonometry
    const bx = ax + link2Length * Math.cos(angleLink2);
    const by = ay + link2Length * Math.sin(angleLink2);
    
    // Verify lengths for debugging (can be removed in production)
    const AB = Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2));
    const BC = Math.sqrt(Math.pow(bx - rightFixedX, 2) + Math.pow(by - rightFixedY, 2));
    
    // Check if lengths match the expected values (within a small tolerance)
    const tolerance = 0.01;
    if (Math.abs(AB - link2Length) > tolerance || Math.abs(BC - link3Length) > tolerance) {
        console.warn("连杆长度不匹配：", // 调试用警告信息
                    "AB =", AB.toFixed(2), "(预期值", link2Length, ")", 
                    "BC =", BC.toFixed(2), "(预期值", link3Length, ")");
    }
    
    return { ax, ay, bx, by };
}

// Draw the linkage on the canvas
function drawLinkage(angle) {
    // Clear the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // Draw the path of point B
    drawPath();
    
    // Convert angle to radians
    const radianAngle = angle;
    
    // Calculate the positions
    const positions = calculatePosition(radianAngle);
    
    // If the linkage can't reach this position, return
    if (!positions) {
        statusMessage.textContent = '错误：连杆机构无法到达此位置';
        statusMessage.className = 'error';
        return;
    }
    
    const { ax, ay, bx, by } = positions;
    
    // Apply current scale and zoom level
    const effectiveScale = scale * zoomLevel;
    
    // Fixed points (scaled and shifted to center of canvas)
    const leftFixedX = centerX - (link0Length / 2) * effectiveScale;
    const leftFixedY = centerY;
    const rightFixedX = centerX + (link0Length / 2) * effectiveScale;
    const rightFixedY = centerY;
    
    // Scaled and shifted positions
    const scaledAx = centerX + ax * effectiveScale;
    const scaledAy = centerY - ay * effectiveScale;
    const scaledBx = centerX + bx * effectiveScale;
    const scaledBy = centerY - by * effectiveScale;
    
    // Add the current position to the path
    addPathPoint(scaledBx, scaledBy);
    
    // Draw ground link
    ctx.beginPath();
    ctx.moveTo(leftFixedX, leftFixedY);
    ctx.lineTo(rightFixedX, rightFixedY);
    ctx.strokeStyle = colors.ground;
    ctx.lineWidth = 4;
    ctx.stroke();
    
    // Draw fixed points (joints)
    drawJoint(leftFixedX, leftFixedY);
    drawJoint(rightFixedX, rightFixedY);
    
    // Draw input link (link 1)
    ctx.beginPath();
    ctx.moveTo(leftFixedX, leftFixedY);
    ctx.lineTo(scaledAx, scaledAy);
    ctx.strokeStyle = colors.link1;
    ctx.lineWidth = 6;
    ctx.stroke();
    
    // Draw coupler link (link 2)
    ctx.beginPath();
    ctx.moveTo(scaledAx, scaledAy);
    ctx.lineTo(scaledBx, scaledBy);
    ctx.strokeStyle = colors.link2;
    ctx.lineWidth = 6;
    ctx.stroke();
    
    // Draw follower link (link 3)
    ctx.beginPath();
    ctx.moveTo(scaledBx, scaledBy);
    ctx.lineTo(rightFixedX, rightFixedY);
    ctx.strokeStyle = colors.link3;
    ctx.lineWidth = 6;
    ctx.stroke();
    
    // Draw joints at points A and B
    drawJoint(scaledAx, scaledAy);
    drawJoint(scaledBx, scaledBy);
    
    // Update angle display
    const degrees = Math.round((angle * 180 / Math.PI) % 360);
    currentAngleDisplay.textContent = `输入杆角度：${degrees}°`; // 显示当前输入杆角度
}

// Draw a joint (circle)
function drawJoint(x, y) {
    ctx.beginPath();
    ctx.arc(x, y, 8, 0, Math.PI * 2);
    ctx.fillStyle = colors.joint;
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x, y, 5, 0, Math.PI * 2);
    ctx.fillStyle = 'white';
    ctx.fill();
}

// Add a point to the path
function addPathPoint(x, y) {
    pathPoints.push({ x, y });
    if (pathPoints.length > maxPathPoints) {
        pathPoints.shift();
    }
}

// Draw the path
function drawPath() {
    if (pathPoints.length < 2) return;
    
    ctx.beginPath();
    ctx.moveTo(pathPoints[0].x, pathPoints[0].y);
    
    for (let i = 1; i < pathPoints.length; i++) {
        ctx.lineTo(pathPoints[i].x, pathPoints[i].y);
    }
    
    ctx.strokeStyle = colors.path;
    ctx.lineWidth = 2;
    ctx.stroke();
}

// Animation loop
function animate() {
    // Update the angle based on speed
    inputAngle += speed * 0.01;
    
    // 检测是否完成了一个完整周期
    if (Math.abs(inputAngle % (2 * Math.PI)) < cycleTolerance && 
        inputAngle > lastCycleStartAngle + 1.5 * Math.PI) { // 确保至少转了270度才检测新周期
        completedCycles++;
        lastCycleStartAngle = inputAngle;
        
        // 如果只想显示一个完整周期的轨迹，可以在此处清空路径点
        // 现在我们保留所有轨迹点，最多保存maxPathPoints个
    }
    
    // Draw the linkage
    drawLinkage(inputAngle);
    
    // Continue the animation if running
    if (isRunning) {
        animationId = requestAnimationFrame(animate);
    }
}

// Start the animation
function startAnimation() {
    if (!isRunning) {
        isRunning = true;
        animate();
    }
}

// Stop the animation
function stopAnimation() {
    isRunning = false;
    if (animationId) {
        cancelAnimationFrame(animationId);
        animationId = null;
    }
}

// Reset the simulation
function resetSimulation() {
    stopAnimation();
    inputAngle = 0;
    
    // Apply the current input values
    link0Length = parseFloat(link0Input.value);
    link1Length = parseFloat(link1Input.value);
    link2Length = parseFloat(link2Input.value);
    link3Length = parseFloat(link3Input.value);
    speed = parseFloat(speedInput.value);
    
    // Clear path points only during explicit reset
    pathPoints = [];
    lastCycleStartAngle = 0;
    completedCycles = 0;
    
    // Apply auto-scaling
    applyAutoScale();
    
    // Initialize the simulation
    init();
}

// Zoom in
function zoomIn() {
    zoomLevel *= 1.2;
    updateZoomLevelDisplay();
    if (!isRunning) {
        drawLinkage(inputAngle);
    }
}

// Zoom out
function zoomOut() {
    zoomLevel /= 1.2;
    updateZoomLevelDisplay();
    if (!isRunning) {
        drawLinkage(inputAngle);
    }
}

// Event listeners
startBtn.addEventListener('click', startAnimation);
stopBtn.addEventListener('click', stopAnimation);
resetBtn.addEventListener('click', resetSimulation);
zoomInBtn.addEventListener('click', zoomIn);
zoomOutBtn.addEventListener('click', zoomOut);
autoScaleBtn.addEventListener('click', applyAutoScale);
document.getElementById('clear-path-btn').addEventListener('click', clearPath);

// Update parameters when inputs change
link0Input.addEventListener('change', resetSimulation);
link1Input.addEventListener('change', resetSimulation);
link2Input.addEventListener('change', resetSimulation);
link3Input.addEventListener('change', resetSimulation);
speedInput.addEventListener('input', () => {
    speed = parseFloat(speedInput.value);
});

// Initialize the simulation with auto-scaling
window.addEventListener('load', () => {
    applyAutoScale();
    init();
});

// Clear path button handler
function clearPath() {
    pathPoints = [];
    if (!isRunning) {
        drawLinkage(inputAngle);
    }
}
